home *** CD-ROM | disk | FTP | other *** search
/ Personal Computer World 2008 February / PCWFEB08.iso / Software / Resources / Developers / XAMPP 1.5.4 / Windows installer / xampp-win32-1.5.4-installer.exe / xampp / php / pear / Text / Wiki.php < prev    next >
Encoding:
PHP Script  |  2006-04-07  |  35.5 KB  |  1,434 lines

  1. <?php
  2. // vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4:
  3. /**
  4.  * Parse structured wiki text and render into arbitrary formats such as XHTML.
  5.  *
  6.  * PHP versions 4 and 5
  7.  *
  8.  * @category   Text
  9.  * @package    Text_Wiki
  10.  * @author     Paul M. Jones <pmjones@php.net>
  11.  * @license    http://www.gnu.org/copyleft/lesser.html  LGPL License 2.1
  12.  * @version    CVS: $Id: Wiki.php,v 1.44 2006/03/02 04:04:59 justinpatrin Exp $
  13.  * @link       http://pear.php.net/package/Text_Wiki
  14.  */
  15.  
  16. /**
  17.  * The baseline abstract parser class.
  18.  */
  19. require_once 'Text/Wiki/Parse.php';
  20.  
  21. /**
  22.  * The baseline abstract render class.
  23.  */
  24. require_once 'Text/Wiki/Render.php';
  25.  
  26. /**
  27.  * Parse structured wiki text and render into arbitrary formats such as XHTML.
  28.  *
  29.  * This is the "master" class for handling the management and convenience
  30.  * functions to transform Wiki-formatted text.
  31.  *
  32.  * @category   Text
  33.  * @package    Text_Wiki
  34.  * @author     Paul M. Jones <pmjones@php.net>
  35.  * @license    http://www.gnu.org/copyleft/lesser.html  LGPL License 2.1
  36.  * @version    Release: 1.1.0
  37.  * @link       http://pear.php.net/package/Text_Wiki
  38.  */
  39. class Text_Wiki {
  40.  
  41.     /**
  42.     *
  43.     * The default list of rules, in order, to apply to the source text.
  44.     *
  45.     * @access public
  46.     *
  47.     * @var array
  48.     *
  49.     */
  50.  
  51.     var $rules = array(
  52.         'Prefilter',
  53.         'Delimiter',
  54.         'Code',
  55.         'Function',
  56.         'Html',
  57.         'Raw',
  58.         'Include',
  59.         'Embed',
  60.         'Anchor',
  61.         'Heading',
  62.         'Toc',
  63.         'Horiz',
  64.         'Break',
  65.         'Blockquote',
  66.         'List',
  67.         'Deflist',
  68.         'Table',
  69.         'Image',
  70.         'Phplookup',
  71.         'Center',
  72.         'Newline',
  73.         'Paragraph',
  74.         'Url',
  75.         'Freelink',
  76.         'Interwiki',
  77.         'Wikilink',
  78.         'Colortext',
  79.         'Strong',
  80.         'Bold',
  81.         'Emphasis',
  82.         'Italic',
  83.         'Underline',
  84.         'Tt',
  85.         'Superscript',
  86.         'Subscript',
  87.         'Revise',
  88.         'Tighten'
  89.     );
  90.  
  91.  
  92.     /**
  93.     *
  94.     * The list of rules to not-apply to the source text.
  95.     *
  96.     * @access public
  97.     *
  98.     * @var array
  99.     *
  100.     */
  101.  
  102.     var $disable = array(
  103.         'Html',
  104.         'Include',
  105.         'Embed'
  106.     );
  107.  
  108.  
  109.     /**
  110.     *
  111.     * Custom configuration for rules at the parsing stage.
  112.     *
  113.     * In this array, the key is the parsing rule name, and the value is
  114.     * an array of key-value configuration pairs corresponding to the $conf
  115.     * property in the target parsing rule.
  116.     *
  117.     * For example:
  118.     *
  119.     * <code>
  120.     * $parseConf = array(
  121.     *     'Include' => array(
  122.     *         'base' => '/path/to/scripts/'
  123.     *     )
  124.     * );
  125.     * </code>
  126.     *
  127.     * Note that most default rules do not need any parsing configuration.
  128.     *
  129.     * @access public
  130.     *
  131.     * @var array
  132.     *
  133.     */
  134.  
  135.     var $parseConf = array();
  136.  
  137.  
  138.     /**
  139.     *
  140.     * Custom configuration for rules at the rendering stage.
  141.     *
  142.     * Because rendering may be different for each target format, the
  143.     * first-level element in this array is always a format name (e.g.,
  144.     * 'Xhtml').
  145.     *
  146.     * Within that first level element, the subsequent elements match the
  147.     * $parseConf format. That is, the sub-key is the rendering rule name,
  148.     * and the sub-value is an array of key-value configuration pairs
  149.     * corresponding to the $conf property in the target rendering rule.
  150.     *
  151.     * @access public
  152.     *
  153.     * @var array
  154.     *
  155.     */
  156.  
  157.     var $renderConf = array(
  158.         'Docbook' => array(),
  159.         'Latex' => array(),
  160.         'Pdf' => array(),
  161.         'Plain' => array(),
  162.         'Rtf' => array(),
  163.         'Xhtml' => array()
  164.     );
  165.  
  166.  
  167.     /**
  168.     *
  169.     * Custom configuration for the output format itself.
  170.     *
  171.     * Even though Text_Wiki will render the tokens from parsed text,
  172.     * the format itself may require some configuration.  For example,
  173.     * RTF needs to know font names and sizes, PDF requires page layout
  174.     * information, and DocBook needs a section hierarchy.  This array
  175.     * matches the $conf property of the the format-level renderer
  176.     * (e.g., Text_Wiki_Render_Xhtml).
  177.     *
  178.     * In this array, the key is the rendering format name, and the value is
  179.     * an array of key-value configuration pairs corresponding to the $conf
  180.     * property in the rendering format rule.
  181.     *
  182.     * @access public
  183.     *
  184.     * @var array
  185.     *
  186.     */
  187.  
  188.     var $formatConf = array(
  189.         'Docbook' => array(),
  190.         'Latex' => array(),
  191.         'Pdf' => array(),
  192.         'Plain' => array(),
  193.         'Rtf' => array(),
  194.         'Xhtml' => array()
  195.     );
  196.  
  197.  
  198.     /**
  199.     *
  200.     * The delimiter for token numbers of parsed elements in source text.
  201.     *
  202.     * @access public
  203.     *
  204.     * @var string
  205.     *
  206.     */
  207.  
  208.     var $delim = "\xFF";
  209.  
  210.  
  211.     /**
  212.     *
  213.     * The tokens generated by rules as the source text is parsed.
  214.     *
  215.     * As Text_Wiki applies rule classes to the source text, it will
  216.     * replace portions of the text with a delimited token number.  This
  217.     * is the array of those tokens, representing the replaced text and
  218.     * any options set by the parser for that replaced text.
  219.     *
  220.     * The tokens array is sequential; each element is itself a sequential
  221.     * array where element 0 is the name of the rule that generated the
  222.     * token, and element 1 is an associative array where the key is an
  223.     * option name and the value is an option value.
  224.     *
  225.     * @access private
  226.     *
  227.     * @var array
  228.     *
  229.     */
  230.  
  231.     var $tokens = array();
  232.  
  233.     /**
  234.     * How many tokens generated pro rules.
  235.     *
  236.     * Intended to load only necessary render objects
  237.     *
  238.     * @access private
  239.     * @var array
  240.     */
  241.     var $_countRulesTokens = array();
  242.  
  243.  
  244.     /**
  245.     *
  246.     * The source text to which rules will be applied.
  247.     *
  248.     * This text will be transformed in-place, which means that it will
  249.     * change as the rules are applied.
  250.     *
  251.     * @access private
  252.     *
  253.     * @var string
  254.     *
  255.     */
  256.  
  257.     var $source = '';
  258.  
  259.  
  260.     /**
  261.     *
  262.     * Array of rule parsers.
  263.     *
  264.     * Text_Wiki creates one instance of every rule that is applied to
  265.     * the source text; this array holds those instances.  The array key
  266.     * is the rule name, and the array value is an instance of the rule
  267.     * class.
  268.     *
  269.     * @access private
  270.     *
  271.     * @var array
  272.     *
  273.     */
  274.  
  275.     var $parseObj = array();
  276.  
  277.  
  278.     /**
  279.     *
  280.     * Array of rule renderers.
  281.     *
  282.     * Text_Wiki creates one instance of every rule that is applied to
  283.     * the source text; this array holds those instances.  The array key
  284.     * is the rule name, and the array value is an instance of the rule
  285.     * class.
  286.     *
  287.     * @access private
  288.     *
  289.     * @var array
  290.     *
  291.     */
  292.  
  293.     var $renderObj = array();
  294.  
  295.  
  296.     /**
  297.     *
  298.     * Array of format renderers.
  299.     *
  300.     * @access private
  301.     *
  302.     * @var array
  303.     *
  304.     */
  305.  
  306.     var $formatObj = array();
  307.  
  308.  
  309.     /**
  310.     *
  311.     * Array of paths to search, in order, for parsing and rendering rules.
  312.     *
  313.     * @access private
  314.     *
  315.     * @var array
  316.     *
  317.     */
  318.  
  319.     var $path = array(
  320.         'parse' => array(),
  321.         'render' => array()
  322.     );
  323.  
  324.  
  325.  
  326.     /**
  327.     *
  328.     * The directory separator character.
  329.     *
  330.     * @access private
  331.     *
  332.     * @var string
  333.     *
  334.     */
  335.  
  336.     var $_dirSep = DIRECTORY_SEPARATOR;
  337.  
  338.  
  339.     /**
  340.     *
  341.     * Constructor.
  342.     *
  343.     * **DEPRECATED**
  344.     * Please use the singleton() or factory() methods.
  345.     *
  346.     * @access public
  347.     *
  348.     * @param array $rules The set of rules to load for this object.  Defaults
  349.     *   to null, which will load the default ruleset for this parser.
  350.     */
  351.  
  352.     function Text_Wiki($rules = null)
  353.     {
  354.         if (is_array($rules)) {
  355.             $this->rules = $rules;
  356.         }
  357.  
  358.         $this->addPath(
  359.             'parse',
  360.             $this->fixPath(dirname(__FILE__)) . 'Wiki/Parse/Default/'
  361.         );
  362.         $this->addPath(
  363.             'render',
  364.             $this->fixPath(dirname(__FILE__)) . 'Wiki/Render/'
  365.         );
  366.  
  367.     }
  368.  
  369.     /**
  370.     * Singleton.
  371.     *
  372.     * This avoids instantiating multiple Text_Wiki instances where a number
  373.     * of objects are required in one call, e.g. to save memory in a
  374.     * CMS invironment where several parsers are required in a single page.
  375.     *
  376.     * $single = & singleton();
  377.     *
  378.     * or
  379.     *
  380.     * $single = & singleton('Parser', array('Prefilter', 'Delimiter', 'Code', 'Function',
  381.     *   'Html', 'Raw', 'Include', 'Embed', 'Anchor', 'Heading', 'Toc', 'Horiz',
  382.     *   'Break', 'Blockquote', 'List', 'Deflist', 'Table', 'Image', 'Phplookup',
  383.     *   'Center', 'Newline', 'Paragraph', 'Url', 'Freelink', 'Interwiki', 'Wikilink',
  384.     *   'Colortext', 'Strong', 'Bold', 'Emphasis', 'Italic', 'Underline', 'Tt',
  385.     *   'Superscript', 'Subscript', 'Revise', 'Tighten'));
  386.     *
  387.     * Call using a subset of this list.  The order of passing rulesets in the
  388.     * $rules array is important!
  389.     *
  390.     * After calling this, call $single->setParseConf(), setRenderConf() or setFormatConf()
  391.     * as usual for a constructed object of this class.
  392.     *
  393.     * The internal static array of singleton objects has no index on the parser
  394.     * rules, the only index is on the parser name.  So if you call this multiple
  395.     * times with different rules but the same parser name, you will get the same
  396.     * static parser object each time.
  397.     *
  398.     * @access public
  399.     * @static
  400.     * @since Method available since Release 1.1.0
  401.     * @param string $parser The parser to be used (defaults to 'Default').
  402.     * @param array $rules   The set of rules to instantiate the object. This
  403.     *    will only be used when the first call to singleton is made, if included
  404.     *    in further calls it will be effectively ignored.
  405.     * @return &object a reference to the Text_Wiki unique instantiation.
  406.     */
  407.     function &singleton($parser = 'Default', $rules = null)
  408.     {
  409.         static $only = array();
  410.         if (!isset($only[$parser])) {
  411.             $ret =& Text_Wiki::factory($parser, $rules);
  412.             if (PEAR::isError($ret)) {
  413.                 return $ret;
  414.             }
  415.             $only[$parser] =& $ret;
  416.         }
  417.         return $only[$parser];
  418.     }
  419.  
  420.     /**
  421.      * Returns a Text_Wiki Parser class for the specified parser.
  422.      *
  423.      * @access public
  424.      * @static
  425.      * @param string $parser The name of the parse to instantiate
  426.      * @param array $rules The rules to pass into the constructor
  427.      *    {@see Text_Wiki::singleton} for a list of rules
  428.      * @return Text_Wiki a Parser object extended from Text_Wiki
  429.      */
  430.     function &factory($parser = 'Default', $rules = null)
  431.     {
  432.         $class = 'Text_Wiki_' . $parser;
  433.         $file = str_replace('_', '/', $class).'.php';
  434.         if (!class_exists($class)) {
  435.             /*$exists = false;
  436.             foreach (split(PATH_SEPARATOR, get_include_path()) as $path) {
  437.                 if (file_exists($path.'/'.$file)
  438.                     && is_readable($path.'/'.$file)) {
  439.                     $exists = true;
  440.                     break;
  441.                 }
  442.             }*/
  443.             $fp = @fopen($file, 'r', true);
  444.             if ($fp === false) {
  445.                 return PEAR::raiseError('Could not find file '.$file.' in include_path');
  446.             }
  447.             fclose($fp);
  448.             include_once($file);
  449.             if (!class_exists($class)) {
  450.                 return PEAR::raiseError('Class '.$class.' does not exist after including '.$file);
  451.             }
  452.         }
  453.  
  454.         $obj =& new $class($rules);
  455.         return $obj;
  456.     }
  457.  
  458.     /**
  459.     *
  460.     * Set parser configuration for a specific rule and key.
  461.     *
  462.     * @access public
  463.     *
  464.     * @param string $rule The parse rule to set config for.
  465.     *
  466.     * @param array|string $arg1 The full config array to use for the
  467.     * parse rule, or a conf key in that array.
  468.     *
  469.     * @param string $arg2 The config value for the key.
  470.     *
  471.     * @return void
  472.     *
  473.     */
  474.  
  475.     function setParseConf($rule, $arg1, $arg2 = null)
  476.     {
  477.         $rule = ucwords(strtolower($rule));
  478.  
  479.         if (! isset($this->parseConf[$rule])) {
  480.             $this->parseConf[$rule] = array();
  481.         }
  482.  
  483.         // if first arg is an array, use it as the entire
  484.         // conf array for the rule.  otherwise, treat arg1
  485.         // as a key and arg2 as a value for the rule conf.
  486.         if (is_array($arg1)) {
  487.             $this->parseConf[$rule] = $arg1;
  488.         } else {
  489.             $this->parseConf[$rule][$arg1] = $arg2;
  490.         }
  491.     }
  492.  
  493.  
  494.     /**
  495.     *
  496.     * Get parser configuration for a specific rule and key.
  497.     *
  498.     * @access public
  499.     *
  500.     * @param string $rule The parse rule to get config for.
  501.     *
  502.     * @param string $key A key in the conf array; if null,
  503.     * returns the entire conf array.
  504.     *
  505.     * @return mixed The whole conf array if no key is specified,
  506.     * or the specific conf key value.
  507.     *
  508.     */
  509.  
  510.     function getParseConf($rule, $key = null)
  511.     {
  512.         $rule = ucwords(strtolower($rule));
  513.  
  514.         // the rule does not exist
  515.         if (! isset($this->parseConf[$rule])) {
  516.             return null;
  517.         }
  518.  
  519.         // no key requested, return the whole array
  520.         if (is_null($key)) {
  521.             return $this->parseConf[$rule];
  522.         }
  523.  
  524.         // does the requested key exist?
  525.         if (isset($this->parseConf[$rule][$key])) {
  526.             // yes, return that value
  527.             return $this->parseConf[$rule][$key];
  528.         } else {
  529.             // no
  530.             return null;
  531.         }
  532.     }
  533.  
  534.  
  535.     /**
  536.     *
  537.     * Set renderer configuration for a specific format, rule, and key.
  538.     *
  539.     * @access public
  540.     *
  541.     * @param string $format The render format to set config for.
  542.     *
  543.     * @param string $rule The render rule to set config for in the format.
  544.     *
  545.     * @param array|string $arg1 The config array, or the config key
  546.     * within the render rule.
  547.     *
  548.     * @param string $arg2 The config value for the key.
  549.     *
  550.     * @return void
  551.     *
  552.     */
  553.  
  554.     function setRenderConf($format, $rule, $arg1, $arg2 = null)
  555.     {
  556.         $format = ucwords(strtolower($format));
  557.         $rule = ucwords(strtolower($rule));
  558.  
  559.         if (! isset($this->renderConf[$format])) {
  560.             $this->renderConf[$format] = array();
  561.         }
  562.  
  563.         if (! isset($this->renderConf[$format][$rule])) {
  564.             $this->renderConf[$format][$rule] = array();
  565.         }
  566.  
  567.         // if first arg is an array, use it as the entire
  568.         // conf array for the render rule.  otherwise, treat arg1
  569.         // as a key and arg2 as a value for the render rule conf.
  570.         if (is_array($arg1)) {
  571.             $this->renderConf[$format][$rule] = $arg1;
  572.         } else {
  573.             $this->renderConf[$format][$rule][$arg1] = $arg2;
  574.         }
  575.     }
  576.  
  577.  
  578.     /**
  579.     *
  580.     * Get renderer configuration for a specific format, rule, and key.
  581.     *
  582.     * @access public
  583.     *
  584.     * @param string $format The render format to get config for.
  585.     *
  586.     * @param string $rule The render format rule to get config for.
  587.     *
  588.     * @param string $key A key in the conf array; if null,
  589.     * returns the entire conf array.
  590.     *
  591.     * @return mixed The whole conf array if no key is specified,
  592.     * or the specific conf key value.
  593.     *
  594.     */
  595.  
  596.     function getRenderConf($format, $rule, $key = null)
  597.     {
  598.         $format = ucwords(strtolower($format));
  599.         $rule = ucwords(strtolower($rule));
  600.  
  601.         if (! isset($this->renderConf[$format]) ||
  602.             ! isset($this->renderConf[$format][$rule])) {
  603.             return null;
  604.         }
  605.  
  606.         // no key requested, return the whole array
  607.         if (is_null($key)) {
  608.             return $this->renderConf[$format][$rule];
  609.         }
  610.  
  611.         // does the requested key exist?
  612.         if (isset($this->renderConf[$format][$rule][$key])) {
  613.             // yes, return that value
  614.             return $this->renderConf[$format][$rule][$key];
  615.         } else {
  616.             // no
  617.             return null;
  618.         }
  619.  
  620.     }
  621.  
  622.     /**
  623.     *
  624.     * Set format configuration for a specific rule and key.
  625.     *
  626.     * @access public
  627.     *
  628.     * @param string $format The format to set config for.
  629.     *
  630.     * @param string $key The config key within the format.
  631.     *
  632.     * @param string $val The config value for the key.
  633.     *
  634.     * @return void
  635.     *
  636.     */
  637.  
  638.     function setFormatConf($format, $arg1, $arg2 = null)
  639.     {
  640.         if (! is_array($this->formatConf[$format])) {
  641.             $this->formatConf[$format] = array();
  642.         }
  643.  
  644.         // if first arg is an array, use it as the entire
  645.         // conf array for the format.  otherwise, treat arg1
  646.         // as a key and arg2 as a value for the format conf.
  647.         if (is_array($arg1)) {
  648.             $this->formatConf[$format] = $arg1;
  649.         } else {
  650.             $this->formatConf[$format][$arg1] = $arg2;
  651.         }
  652.     }
  653.  
  654.  
  655.  
  656.     /**
  657.     *
  658.     * Get configuration for a specific format and key.
  659.     *
  660.     * @access public
  661.     *
  662.     * @param string $format The format to get config for.
  663.     *
  664.     * @param mixed $key A key in the conf array; if null,
  665.     * returns the entire conf array.
  666.     *
  667.     * @return mixed The whole conf array if no key is specified,
  668.     * or the specific conf key value.
  669.     *
  670.     */
  671.  
  672.     function getFormatConf($format, $key = null)
  673.     {
  674.         // the format does not exist
  675.         if (! isset($this->formatConf[$format])) {
  676.             return null;
  677.         }
  678.  
  679.         // no key requested, return the whole array
  680.         if (is_null($key)) {
  681.             return $this->formatConf[$format];
  682.         }
  683.  
  684.         // does the requested key exist?
  685.         if (isset($this->formatConf[$format][$key])) {
  686.             // yes, return that value
  687.             return $this->formatConf[$format][$key];
  688.         } else {
  689.             // no
  690.             return null;
  691.         }
  692.     }
  693.  
  694.  
  695.     /**
  696.     *
  697.     * Inserts a rule into to the rule set.
  698.     *
  699.     * @access public
  700.     *
  701.     * @param string $name The name of the rule.  Should be different from
  702.     * all other keys in the rule set.
  703.     *
  704.     * @param string $tgt The rule after which to insert this new rule.  By
  705.     * default (null) the rule is inserted at the end; if set to '', inserts
  706.     * at the beginning.
  707.     *
  708.     * @return void
  709.     *
  710.     */
  711.  
  712.     function insertRule($name, $tgt = null)
  713.     {
  714.         $name = ucwords(strtolower($name));
  715.         if (! is_null($tgt)) {
  716.             $tgt = ucwords(strtolower($tgt));
  717.         }
  718.  
  719.         // does the rule name to be inserted already exist?
  720.         if (in_array($name, $this->rules)) {
  721.             // yes, return
  722.             return null;
  723.         }
  724.  
  725.         // the target name is not null, and not '', but does not exist
  726.         // in the list of rules. this means we're trying to insert after
  727.         // a target key, but the target key isn't there.
  728.         if (! is_null($tgt) && $tgt != '' &&
  729.             ! in_array($tgt, $this->rules)) {
  730.             return false;
  731.         }
  732.  
  733.         // if $tgt is null, insert at the end.  We know this is at the
  734.         // end (instead of resetting an existing rule) becuase we exited
  735.         // at the top of this method if the rule was already in place.
  736.         if (is_null($tgt)) {
  737.             $this->rules[] = $name;
  738.             return true;
  739.         }
  740.  
  741.         // save a copy of the current rules, then reset the rule set
  742.         // so we can insert in the proper place later.
  743.         // where to insert the rule?
  744.         if ($tgt == '') {
  745.             // insert at the beginning
  746.             array_unshift($this->rules, $name);
  747.             return true;
  748.         }
  749.  
  750.         // insert after the named rule
  751.         $tmp = $this->rules;
  752.         $this->rules = array();
  753.  
  754.         foreach ($tmp as $val) {
  755.             $this->rules[] = $val;
  756.             if ($val == $tgt) {
  757.                 $this->rules[] = $name;
  758.             }
  759.         }
  760.  
  761.         return true;
  762.  
  763.     }
  764.  
  765.  
  766.     /**
  767.     *
  768.     * Delete (remove or unset) a rule from the $rules property.
  769.     *
  770.     * @access public
  771.     *
  772.     * @param string $rule The name of the rule to remove.
  773.     *
  774.     * @return void
  775.     *
  776.     */
  777.  
  778.     function deleteRule($name)
  779.     {
  780.         $name = ucwords(strtolower($name));
  781.         $key = array_search($name, $this->rules);
  782.         if ($key !== false) {
  783.             unset($this->rules[$key]);
  784.         }
  785.     }
  786.  
  787.  
  788.     /**
  789.     *
  790.     * Change from one rule to another in-place.
  791.     *
  792.     * @access public
  793.     *
  794.     * @param string $old The name of the rule to change from.
  795.     *
  796.     * @param string $new The name of the rule to change to.
  797.     *
  798.     * @return void
  799.     *
  800.     */
  801.  
  802.     function changeRule($old, $new)
  803.     {
  804.         $old = ucwords(strtolower($old));
  805.         $new = ucwords(strtolower($new));
  806.         $key = array_search($old, $this->rules);
  807.         if ($key !== false) {
  808.             // delete the new name , case it was already there
  809.             $this->deleteRule($new);
  810.             $this->rules[$key] = $new;
  811.         }
  812.     }
  813.  
  814.  
  815.     /**
  816.     *
  817.     * Enables a rule so that it is applied when parsing.
  818.     *
  819.     * @access public
  820.     *
  821.     * @param string $rule The name of the rule to enable.
  822.     *
  823.     * @return void
  824.     *
  825.     */
  826.  
  827.     function enableRule($name)
  828.     {
  829.         $name = ucwords(strtolower($name));
  830.         $key = array_search($name, $this->disable);
  831.         if ($key !== false) {
  832.             unset($this->disable[$key]);
  833.         }
  834.     }
  835.  
  836.  
  837.     /**
  838.     *
  839.     * Disables a rule so that it is not applied when parsing.
  840.     *
  841.     * @access public
  842.     *
  843.     * @param string $rule The name of the rule to disable.
  844.     *
  845.     * @return void
  846.     *
  847.     */
  848.  
  849.     function disableRule($name)
  850.     {
  851.         $name = ucwords(strtolower($name));
  852.         $key = array_search($name, $this->disable);
  853.         if ($key === false) {
  854.             $this->disable[] = $name;
  855.         }
  856.     }
  857.  
  858.  
  859.     /**
  860.     *
  861.     * Parses and renders the text passed to it, and returns the results.
  862.     *
  863.     * First, the method parses the source text, applying rules to the
  864.     * text as it goes.  These rules will modify the source text
  865.     * in-place, replacing some text with delimited tokens (and
  866.     * populating the $this->tokens array as it goes).
  867.     *
  868.     * Next, the method renders the in-place tokens into the requested
  869.     * output format.
  870.     *
  871.     * Finally, the method returns the transformed text.  Note that the
  872.     * source text is transformed in place; once it is transformed, it is
  873.     * no longer the same as the original source text.
  874.     *
  875.     * @access public
  876.     *
  877.     * @param string $text The source text to which wiki rules should be
  878.     * applied, both for parsing and for rendering.
  879.     *
  880.     * @param string $format The target output format, typically 'xhtml'.
  881.     *  If a rule does not support a given format, the output from that
  882.     * rule is rule-specific.
  883.     *
  884.     * @return string The transformed wiki text.
  885.     *
  886.     */
  887.  
  888.     function transform($text, $format = 'Xhtml')
  889.     {
  890.         $this->parse($text);
  891.         return $this->render($format);
  892.     }
  893.  
  894.  
  895.     /**
  896.     *
  897.     * Sets the $_source text property, then parses it in place and
  898.     * retains tokens in the $_tokens array property.
  899.     *
  900.     * @access public
  901.     *
  902.     * @param string $text The source text to which wiki rules should be
  903.     * applied, both for parsing and for rendering.
  904.     *
  905.     * @return void
  906.     *
  907.     */
  908.  
  909.     function parse($text)
  910.     {
  911.         // set the object property for the source text
  912.         $this->source = $text;
  913.  
  914.         // reset the tokens.
  915.         $this->tokens = array();
  916.         $this->_countRulesTokens = array();
  917.  
  918.         // apply the parse() method of each requested rule to the source
  919.         // text.
  920.         foreach ($this->rules as $name) {
  921.             // do not parse the rules listed in $disable
  922.             if (! in_array($name, $this->disable)) {
  923.  
  924.                 // load the parsing object
  925.                 $this->loadParseObj($name);
  926.  
  927.                 // load may have failed; only parse if
  928.                 // an object is in the array now
  929.                 if (is_object($this->parseObj[$name])) {
  930.                     $this->parseObj[$name]->parse();
  931.                 }
  932.             }
  933.         }
  934.     }
  935.  
  936.  
  937.     /**
  938.     *
  939.     * Renders tokens back into the source text, based on the requested format.
  940.     *
  941.     * @access public
  942.     *
  943.     * @param string $format The target output format, typically 'xhtml'.
  944.     * If a rule does not support a given format, the output from that
  945.     * rule is rule-specific.
  946.     *
  947.     * @return string The transformed wiki text.
  948.     *
  949.     */
  950.  
  951.     function render($format = 'Xhtml')
  952.     {
  953.         // the rendering method we're going to use from each rule
  954.         $format = ucwords(strtolower($format));
  955.  
  956.         // the eventual output text
  957.         $output = '';
  958.  
  959.         // when passing through the parsed source text, keep track of when
  960.         // we are in a delimited section
  961.         $in_delim = false;
  962.  
  963.         // when in a delimited section, capture the token key number
  964.         $key = '';
  965.  
  966.         // load the format object, or crap out if we can't find it
  967.         $result = $this->loadFormatObj($format);
  968.         if ($this->isError($result)) {
  969.             return $result;
  970.         }
  971.  
  972.         // pre-rendering activity
  973.         if (is_object($this->formatObj[$format])) {
  974.             $output .= $this->formatObj[$format]->pre();
  975.         }
  976.  
  977.         // load the render objects
  978.         foreach (array_keys($this->_countRulesTokens) as $rule) {
  979.             $this->loadRenderObj($format, $rule);
  980.         }
  981.  
  982.         // pass through the parsed source text character by character
  983.         $k = strlen($this->source);
  984.         for ($i = 0; $i < $k; $i++) {
  985.  
  986.             // the current character
  987.             $char = $this->source{$i};
  988.  
  989.             // are alredy in a delimited section?
  990.             if ($in_delim) {
  991.  
  992.                 // yes; are we ending the section?
  993.                 if ($char == $this->delim) {
  994.  
  995.                     // yes, get the replacement text for the delimited
  996.                     // token number and unset the flag.
  997.                     $key = (int)$key;
  998.                     $rule = $this->tokens[$key][0];
  999.                     $opts = $this->tokens[$key][1];
  1000.                     $output .= $this->renderObj[$rule]->token($opts);
  1001.                     $in_delim = false;
  1002.  
  1003.                 } else {
  1004.  
  1005.                     // no, add to the dlimited token key number
  1006.                     $key .= $char;
  1007.  
  1008.                 }
  1009.  
  1010.             } else {
  1011.  
  1012.                 // not currently in a delimited section.
  1013.                 // are we starting into a delimited section?
  1014.                 if ($char == $this->delim) {
  1015.                     // yes, reset the previous key and
  1016.                     // set the flag.
  1017.                     $key = '';
  1018.                     $in_delim = true;
  1019.                 } else {
  1020.                     // no, add to the output as-is
  1021.                     $output .= $char;
  1022.                 }
  1023.             }
  1024.         }
  1025.  
  1026.         // post-rendering activity
  1027.         if (is_object($this->formatObj[$format])) {
  1028.             $output .= $this->formatObj[$format]->post();
  1029.         }
  1030.  
  1031.         // return the rendered source text.
  1032.         return $output;
  1033.     }
  1034.  
  1035.  
  1036.     /**
  1037.     *
  1038.     * Returns the parsed source text with delimited token placeholders.
  1039.     *
  1040.     * @access public
  1041.     *
  1042.     * @return string The parsed source text.
  1043.     *
  1044.     */
  1045.  
  1046.     function getSource()
  1047.     {
  1048.         return $this->source;
  1049.     }
  1050.  
  1051.  
  1052.     /**
  1053.     *
  1054.     * Returns tokens that have been parsed out of the source text.
  1055.     *
  1056.     * @access public
  1057.     *
  1058.     * @param array $rules If an array of rule names is passed, only return
  1059.     * tokens matching these rule names.  If no array is passed, return all
  1060.     * tokens.
  1061.     *
  1062.     * @return array An array of tokens.
  1063.     *
  1064.     */
  1065.  
  1066.     function getTokens($rules = null)
  1067.     {
  1068.         if (is_null($rules)) {
  1069.             return $this->tokens;
  1070.         } else {
  1071.             settype($rules, 'array');
  1072.             $result = array();
  1073.             foreach ($this->tokens as $key => $val) {
  1074.                 if (in_array($val[0], $rules)) {
  1075.                     $result[$key] = $val;
  1076.                 }
  1077.             }
  1078.             return $result;
  1079.         }
  1080.     }
  1081.  
  1082.  
  1083.     /**
  1084.     *
  1085.     * Add a token to the Text_Wiki tokens array, and return a delimited
  1086.     * token number.
  1087.     *
  1088.     * @access public
  1089.     *
  1090.     * @param array $options An associative array of options for the new
  1091.     * token array element.  The keys and values are specific to the
  1092.     * rule, and may or may not be common to other rule options.  Typical
  1093.     * options keys are 'text' and 'type' but may include others.
  1094.     *
  1095.     * @param boolean $id_only If true, return only the token number, not
  1096.     * a delimited token string.
  1097.     *
  1098.     * @return string|int By default, return the number of the
  1099.     * newly-created token array element with a delimiter prefix and
  1100.     * suffix; however, if $id_only is set to true, return only the token
  1101.     * number (no delimiters).
  1102.     *
  1103.     */
  1104.  
  1105.     function addToken($rule, $options = array(), $id_only = false)
  1106.     {
  1107.         // increment the token ID number.  note that if you parse
  1108.         // multiple times with the same Text_Wiki object, the ID number
  1109.         // will not reset to zero.
  1110.         static $id;
  1111.         if (! isset($id)) {
  1112.             $id = 0;
  1113.         } else {
  1114.             $id ++;
  1115.         }
  1116.  
  1117.         // force the options to be an array
  1118.         settype($options, 'array');
  1119.  
  1120.         // add the token
  1121.         $this->tokens[$id] = array(
  1122.             0 => $rule,
  1123.             1 => $options
  1124.         );
  1125.         if (!isset($this->_countRulesTokens[$rule])) {
  1126.             $this->_countRulesTokens[$rule] = 1;
  1127.         } else {
  1128.             ++$this->_countRulesTokens[$rule];
  1129.         }
  1130.  
  1131.         // return a value
  1132.         if ($id_only) {
  1133.             // return the last token number
  1134.             return $id;
  1135.         } else {
  1136.             // return the token number with delimiters
  1137.             return $this->delim . $id . $this->delim;
  1138.         }
  1139.     }
  1140.  
  1141.  
  1142.     /**
  1143.     *
  1144.     * Set or re-set a token with specific information, overwriting any
  1145.     * previous rule name and rule options.
  1146.     *
  1147.     * @access public
  1148.     *
  1149.     * @param int $id The token number to reset.
  1150.     *
  1151.     * @param int $rule The rule name to use.
  1152.     *
  1153.     * @param array $options An associative array of options for the
  1154.     * token array element.  The keys and values are specific to the
  1155.     * rule, and may or may not be common to other rule options.  Typical
  1156.     * options keys are 'text' and 'type' but may include others.
  1157.     *
  1158.     * @return void
  1159.     *
  1160.     */
  1161.  
  1162.     function setToken($id, $rule, $options = array())
  1163.     {
  1164.         $oldRule = $this->tokens[$id][0];
  1165.         // reset the token
  1166.         $this->tokens[$id] = array(
  1167.             0 => $rule,
  1168.             1 => $options
  1169.         );
  1170.         if ($rule != $oldRule) {
  1171.             if (!($this->_countRulesTokens[$oldRule]--)) {
  1172.                 unset($this->_countRulesTokens[$oldRule]);
  1173.             }
  1174.             if (!isset($this->_countRulesTokens[$rule])) {
  1175.                 $this->_countRulesTokens[$rule] = 1;
  1176.             } else {
  1177.                 ++$this->_countRulesTokens[$rule];
  1178.             }
  1179.         }
  1180.     }
  1181.  
  1182.  
  1183.     /**
  1184.     *
  1185.     * Load a rule parser class file.
  1186.     *
  1187.     * @access public
  1188.     *
  1189.     * @return bool True if loaded, false if not.
  1190.     *
  1191.     */
  1192.  
  1193.     function loadParseObj($rule)
  1194.     {
  1195.         $rule = ucwords(strtolower($rule));
  1196.         $file = $rule . '.php';
  1197.         $class = "Text_Wiki_Parse_$rule";
  1198.  
  1199.         if (! class_exists($class)) {
  1200.             $loc = $this->findFile('parse', $file);
  1201.             if ($loc) {
  1202.                 // found the class
  1203.                 include_once $loc;
  1204.             } else {
  1205.                 // can't find the class
  1206.                 $this->parseObj[$rule] = null;
  1207.                 // can't find the class
  1208.                 return $this->error(
  1209.                     "Parse rule '$rule' not found"
  1210.                 );
  1211.             }
  1212.         }
  1213.  
  1214.         $this->parseObj[$rule] =& new $class($this);
  1215.  
  1216.     }
  1217.  
  1218.  
  1219.     /**
  1220.     *
  1221.     * Load a rule-render class file.
  1222.     *
  1223.     * @access public
  1224.     *
  1225.     * @return bool True if loaded, false if not.
  1226.     *
  1227.     */
  1228.  
  1229.     function loadRenderObj($format, $rule)
  1230.     {
  1231.         $format = ucwords(strtolower($format));
  1232.         $rule = ucwords(strtolower($rule));
  1233.         $file = "$format/$rule.php";
  1234.         $class = "Text_Wiki_Render_$format" . "_$rule";
  1235.  
  1236.         if (! class_exists($class)) {
  1237.             // load the class
  1238.             $loc = $this->findFile('render', $file);
  1239.             if ($loc) {
  1240.                 // found the class
  1241.                 include_once $loc;
  1242.             } else {
  1243.                 // can't find the class
  1244.                 return $this->error(
  1245.                     "Render rule '$rule' in format '$format' not found"
  1246.                 );
  1247.             }
  1248.         }
  1249.  
  1250.         $this->renderObj[$rule] =& new $class($this);
  1251.     }
  1252.  
  1253.  
  1254.     /**
  1255.     *
  1256.     * Load a format-render class file.
  1257.     *
  1258.     * @access public
  1259.     *
  1260.     * @return bool True if loaded, false if not.
  1261.     *
  1262.     */
  1263.  
  1264.     function loadFormatObj($format)
  1265.     {
  1266.         $format = ucwords(strtolower($format));
  1267.         $file = $format . '.php';
  1268.         $class = "Text_Wiki_Render_$format";
  1269.  
  1270.         if (! class_exists($class)) {
  1271.             $loc = $this->findFile('render', $file);
  1272.             if ($loc) {
  1273.                 // found the class
  1274.                 include_once $loc;
  1275.             } else {
  1276.                 // can't find the class
  1277.                 return $this->error(
  1278.                     "Rendering format class '$class' not found"
  1279.                 );
  1280.             }
  1281.         }
  1282.  
  1283.         $this->formatObj[$format] =& new $class($this);
  1284.     }
  1285.  
  1286.  
  1287.     /**
  1288.     *
  1289.     * Add a path to a path array.
  1290.     *
  1291.     * @access public
  1292.     *
  1293.     * @param string $type The path-type to add (parse or render).
  1294.     *
  1295.     * @param string $dir The directory to add to the path-type.
  1296.     *
  1297.     * @return void
  1298.     *
  1299.     */
  1300.  
  1301.     function addPath($type, $dir)
  1302.     {
  1303.         $dir = $this->fixPath($dir);
  1304.         if (! isset($this->path[$type])) {
  1305.             $this->path[$type] = array($dir);
  1306.         } else {
  1307.             array_unshift($this->path[$type], $dir);
  1308.         }
  1309.     }
  1310.  
  1311.  
  1312.     /**
  1313.     *
  1314.     * Get the current path array for a path-type.
  1315.     *
  1316.     * @access public
  1317.     *
  1318.     * @param string $type The path-type to look up (plugin, filter, or
  1319.     * template).  If not set, returns all path types.
  1320.     *
  1321.     * @return array The array of paths for the requested type.
  1322.     *
  1323.     */
  1324.  
  1325.     function getPath($type = null)
  1326.     {
  1327.         if (is_null($type)) {
  1328.             return $this->path;
  1329.         } elseif (! isset($this->path[$type])) {
  1330.             return array();
  1331.         } else {
  1332.             return $this->path[$type];
  1333.         }
  1334.     }
  1335.  
  1336.  
  1337.     /**
  1338.     *
  1339.     * Searches a series of paths for a given file.
  1340.     *
  1341.     * @param array $type The type of paths to search (template, plugin,
  1342.     * or filter).
  1343.     *
  1344.     * @param string $file The file name to look for.
  1345.     *
  1346.     * @return string|bool The full path and file name for the target file,
  1347.     * or boolean false if the file is not found in any of the paths.
  1348.     *
  1349.     */
  1350.  
  1351.     function findFile($type, $file)
  1352.     {
  1353.         // get the set of paths
  1354.         $set = $this->getPath($type);
  1355.  
  1356.         // start looping through them
  1357.         foreach ($set as $path) {
  1358.             $fullname = $path . $file;
  1359.             if (file_exists($fullname) && is_readable($fullname)) {
  1360.                 return $fullname;
  1361.             }
  1362.         }
  1363.  
  1364.         // could not find the file in the set of paths
  1365.         return false;
  1366.     }
  1367.  
  1368.  
  1369.     /**
  1370.     *
  1371.     * Append a trailing '/' to paths, unless the path is empty.
  1372.     *
  1373.     * @access private
  1374.     *
  1375.     * @param string $path The file path to fix
  1376.     *
  1377.     * @return string The fixed file path
  1378.     *
  1379.     */
  1380.  
  1381.     function fixPath($path)
  1382.     {
  1383.         $len = strlen($this->_dirSep);
  1384.  
  1385.         if (! empty($path) &&
  1386.             substr($path, -1 * $len, $len) != $this->_dirSep)    {
  1387.             return $path . $this->_dirSep;
  1388.         } else {
  1389.             return $path;
  1390.         }
  1391.     }
  1392.  
  1393.  
  1394.     /**
  1395.     *
  1396.     * Simple error-object generator.
  1397.     *
  1398.     * @access public
  1399.     *
  1400.     * @param string $message The error message.
  1401.     *
  1402.     * @return object PEAR_Error
  1403.     *
  1404.     */
  1405.  
  1406.     function &error($message)
  1407.     {
  1408.         if (! class_exists('PEAR_Error')) {
  1409.             include_once 'PEAR.php';
  1410.         }
  1411.         return PEAR::throwError($message);
  1412.     }
  1413.  
  1414.  
  1415.     /**
  1416.     *
  1417.     * Simple error checker.
  1418.     *
  1419.     * @access public
  1420.     *
  1421.     * @param mixed $obj Check if this is a PEAR_Error object or not.
  1422.     *
  1423.     * @return bool True if a PEAR_Error, false if not.
  1424.     *
  1425.     */
  1426.  
  1427.     function isError(&$obj)
  1428.     {
  1429.         return is_a($obj, 'PEAR_Error');
  1430.     }
  1431. }
  1432.  
  1433. ?>
  1434.